// ----------------------------------------------------------------------------
// Copyright 2006, Martin D. Flynn
// All rights reserved
// ----------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ----------------------------------------------------------------------------
// Description:
//  This class provides the display for GPS information (lat, lon, speed, etc).
//  This is the main display for the midlet application.
// ----------------------------------------------------------------------------
// Change History:
//  2006/03/26  Martin D. Flynn
//      Initial release
//  2006/03/31  Martin D. Flynn
//      Placed in custom sub-pakage to allow device dependent UI packaging
//      Added 'Waymark' command to UI
// ----------------------------------------------------------------------------
package org.opendmtp.j2me.client.custom.treo650;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;

import org.opendmtp.j2me.util.Log;
import org.opendmtp.j2me.util.GeoPoint;
import org.opendmtp.j2me.util.GeoEvent;
import org.opendmtp.j2me.util.StringTools;
import org.opendmtp.j2me.codes.StatusCodes;
import org.opendmtp.j2me.client.base.Props;
import org.opendmtp.j2me.client.base.Packet;
import org.opendmtp.j2me.client.base.Protocol;
import org.opendmtp.j2me.client.gps.GPSReceiver;

/**
 * Provides the display for GPS information (lat, lon, speed, etc). This is the main display for the
 * MIDlet application.
 * 
 * @author Martin D. Flynn.
 * @author Pavel V. Senin.
 * 
 */
public class GPSDisplay extends Form implements CommandListener {

  /**
   * Specifies string constant for the log name.
   */
  private static final String LOG_NAME = "DISP";

  /**
   * Specifies title of the display form.
   */
  private static final String TITLE = "OpenDMTP Tracker";

  /**
   * Holds GPSDisplay implementation.
   */
  private static GPSDisplay gpsDisplay = null;

  /**
   * Returns instance of this GPSDisplay object. If no object exists creates new one.
   * 
   * @return instance of the object.
   */
  public static GPSDisplay getInstance() {
    if (gpsDisplay == null) {
      gpsDisplay = new GPSDisplay();
    }
    return gpsDisplay;
  }

  /**
   * Latitude field.
   */
  private GPSField fldLat = null;
  /**
   * Longitude field.
   */
  private GPSField fldLon = null;
  /**
   * Speed field.
   */
  private GPSField fldSpeed = null;
  /**
   * Altitude field.
   */
  private GPSField fldAltitude = null;
  /**
   * Message0.
   */
  private GPSField fldMessage0 = null;
  /**
   * Message1.
   */
  private GPSField fldMessage1 = null;
  /**
   * Ping command.
   */
  private Command cmdPing = null;
  /**
   * Options command.
   */
  private Command cmdOpts = null;
  /**
   * Exit command.
   */
  private Command cmdExit = null;

  /**
   * Initializes display with predefined values.
   */
  private GPSDisplay() {
    super(TITLE + " v" + Main.DMTP_VERS);

    /* fields */
    try {
      this.fldLat = this.addField("LAT: ");
      this.fldLon = this.addField("LON: ");
      this.fldSpeed = this.addField("SPD: ");
      this.fldAltitude = this.addField("ALT: ");
      // this.addField(null); // blank separator
      this.fldMessage0 = this.addField(null);
      this.fldMessage1 = this.addField(null);
    }
    catch (Throwable t) {
      Log.error(LOG_NAME, "Field init", t);
    }

    /* commands */
    try {
      this.cmdPing = this.addCommand("Waymark", "Waymark", Command.ITEM, 1);
      this.cmdOpts = this.addCommand("Options", "Options...", Command.ITEM, 2);
      this.cmdExit = this.addCommand("Exit", "Exit", Command.EXIT, 5);
      super.setCommandListener(this);
    }
    catch (Throwable t) {
      Log.error(LOG_NAME, "Command init", t);
    }

  }

  /**
   * Reports whether this GPSDisplay is visible.
   * 
   * @return true if visible.
   */
  public boolean isVisible() {
    return (Main.getDisplay().getCurrent() == this);
  }

  /**
   * Updates display when an GPS event is encountered. This class suppose to be thread safe.
   * 
   * @param gev GPS event.
   */
  public void updateValues(GeoEvent gev) {
    // "http://java.sun.com/developer/J2METechTips/2001/tt0917.html" show an example
    // that updates a displayed "StringItem" from a child thread, so I'm assuming that
    // the underlying display system is thread safe.

    /* display is visible? */
    if (!this.isVisible()) {
      return;
    }

    /* validate GeoEvent */
    if (gev == null) {
      return;
    }

    /* lat/lon */
    GeoPoint gp = gev.getGeoPoint();
    this.fldLat.setValueText(gp.getLatitudeString());
    this.fldLon.setValueText(gp.getLongitudeString());

    /* speed/heading */
    double spdMPH = gev.getSpeedMPH();
    String mphStr = StringTools.formatDouble(spdMPH, 1);
    String degStr = (spdMPH > 0.0) ? GeoPoint.GetHeadingString(gev.getHeading()) : "";
    this.fldSpeed.setValueText(mphStr + " mph  " + degStr);

    /* altitude */
    this.fldAltitude.setValueText(StringTools.formatDouble(gev.getAltitudeFeet(), 0) + " ft");

  }

  /**
   * Sets the message value for the GPS field.
   * 
   * @param ndx GPSDisplay field index.
   * @param msg message to set.
   */
  public static void setMessage(int ndx, String msg) {
    GPSDisplay gpsd = GPSDisplay.getInstance();
    GPSField gpsf = (ndx == 0) ? gpsd.fldMessage0 : gpsd.fldMessage1;
    gpsf.setValueText(msg);
  }

  /**
   * Adds new field to display.
   * 
   * @param labelText label for the added field.
   * @return created field.
   */
  private GPSField addField(String labelText) {
    GPSField fld = new GPSField(labelText);
    StringItem label = fld.getLabelItem();
    if (label != null) {
      GPSDisplay.super.append(label);
    }
    StringItem value = fld.getValueItem();
    if (value != null) {
      GPSDisplay.super.append(value);
    }
    return fld;
  }

  /**
   * Adds a new command to display.
   * 
   * @param shortLbl short command abbreviation.
   * @param longLbl long command abbreviation.
   * @param type command type.
   * @param pri command priority.
   * @return added command.
   */
  private Command addCommand(String shortLbl, String longLbl, int type, int pri) {
    Command cmd = new Command(shortLbl, longLbl, type, pri);
    super.addCommand(cmd);
    return cmd;
  }

  /**
   * Accepts commands and cause display to react accordingly.
   * 
   * @param c command to parse.
   * @param d unused.
   */
  public void commandAction(Command c, Displayable d) {
    try {
      if (c == this.cmdExit) {
        Main.getInstance().exitApp();
      }
      else if (c == this.cmdPing) {
        GeoEvent gev = new GeoEvent();
        GPSReceiver.getInstance().aquire(-1L, gev);
        gev.setStatusCode(StatusCodes.STATUS_WAYMARK);
        gev.checkMinimumSpeed(Props.getDouble(Props.PROP_GPS_MIN_SPEED, 0, 7.0));
        Protocol.getInstance().getEventQueue().addEvent(Packet.PRIORITY_NORMAL, gev);
      }
      else if (c == this.cmdOpts) {
        OptionsScreen.show();
      }
    }
    catch (Throwable t) {
      AlertScreen.showError("Error", "Command: " + t);
    }
  }

  /**
   * Defines label font.
   */
  private static final int LABEL_FONT_FACE = Font.FACE_MONOSPACE;
  /**
   * Defines value font.
   */
  private static final int VALUE_FONT_FACE = Font.FACE_MONOSPACE;
  /**
   * Defines message font.
   */
  private static final int MESSG_FONT_FACE = Font.FACE_MONOSPACE;
  /**
   * Defines label font size.
   */
  private static final int LABEL_FONT_SIZE = Font.SIZE_LARGE;
  /**
   * Defines value font size.
   */
  private static final int VALUE_FONT_SIZE = Font.SIZE_LARGE;
  /**
   * Defines message font size.
   */
  private static final int MESSG_FONT_SIZE = Font.SIZE_MEDIUM;

  /**
   * Encapsulates all needed information (text, text size, color, etc...) to display label at the
   * screen.
   * 
   * @author Martin D. Flynn.
   * @author Pavel V. Senin.
   */
  private class GPSField {
    /**
     * Label text.
     */
    private StringItem labelItem = null;
    /**
     * Label value.
     */
    private StringItem valueItem = null;

    /**
     * 
     * Creates new GPSfield.
     * 
     * @param labelText text field for this label.
     */
    public GPSField(String labelText) {
      try {
        Font valueFont = null;
        if (labelText != null) {
          this.labelItem = new StringItem(null, labelText);
          this.labelItem.setFont(Font.getFont(LABEL_FONT_FACE, Font.STYLE_BOLD, LABEL_FONT_SIZE));
          valueFont = Font.getFont(VALUE_FONT_FACE, Font.STYLE_PLAIN, VALUE_FONT_SIZE);
        }
        else {
          this.labelItem = null;
          valueFont = Font.getFont(MESSG_FONT_FACE, Font.STYLE_BOLD, MESSG_FONT_SIZE);
        }
        this.valueItem = new StringItem(null, "");
        this.valueItem.setFont(valueFont);
        this.setValueText("");
      }
      catch (Throwable t) {
        Log.error(LOG_NAME, "Unable to create field", t);
      }
    }

    /**
     * Returns label text.
     * 
     * @return label text.
     */
    public StringItem getLabelItem() {
      return this.labelItem;
    }

    /**
     * Returns label value.
     * 
     * @return label value.
     */
    public StringItem getValueItem() {
      return this.valueItem;
    }

    /**
     * Sets label value.
     * 
     * @param valueText value to set.
     */
    public void setValueText(String valueText) {
      try {
        this.valueItem.setText(valueText + "\n");
      }
      catch (Throwable t) {
        Log.error(LOG_NAME, "Unable to set field value", t);
      }
    }
  }

}
